﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Shapes;


namespace UMLTouch.Core.Entities
{


    public class Segment : IEngineEntity, IEquatable<Segment>
    {

        double MaxDist = 40;

        #region Fields

        Point _startPoint;
        Point _endPoint;

        double _mostTopY;

        double _mostBottomY;

        double _mostLeftX;

        double _mostRightX;




        Stroke _stroke;

        #endregion

        #region Properties

        public double MostTopY
        {
            get { return _mostTopY; }

        }
        public double MostBottomY
        {
            get { return _mostBottomY; }

        }
        public double MostLeftX
        {
            get { return _mostLeftX; }

        }
        public double MostRightX
        {
            get { return _mostRightX; }

        }

        public Double Length
        {
            get
            {
                return ToVector().Length;
            }
        }

        public Point StartPoint
        {
            get { return _startPoint; }
            set
            {
                _startPoint = value;
                calculateMosts();
            }
        }

        public Stroke Stroke
        {
            get { return _stroke; }
            set { _stroke = value; }
        }
        public Point EndPoint
        {
            get { return _endPoint; }
            set
            {
                _endPoint = value;
                calculateMosts();
            }
        }

        public IEnumerable<Point> Points {
            get
            {
                return new Point[] { StartPoint,EndPoint};
            }

        
        }

        #endregion

        #region Constructors

        public Segment(Stroke stroke,Point startPoint, Point endPoint)
        {
            Stroke = stroke;
            StartPoint = startPoint;
            EndPoint = endPoint;
        }

        #endregion

        #region Methods

        private void calculateMosts()
        {
            if (StartPoint.X >= EndPoint.X)
            {
                _mostRightX = StartPoint.X;
                _mostLeftX = EndPoint.X;
            }
            else
            {
                _mostLeftX = StartPoint.X;
                _mostRightX = EndPoint.X;
            }

            if (StartPoint.Y >= EndPoint.Y)
            {
                _mostTopY = EndPoint.Y;
                _mostBottomY = StartPoint.Y;
            }
            else
            {
                _mostBottomY = EndPoint.Y;
                _mostTopY = StartPoint.Y;
            }
        }


        public System.Windows.Ink.Stroke toStylusStroke()
        {
            System.Windows.Input.StylusPointCollection col = new System.Windows.Input.StylusPointCollection();
            col.Add(new StylusPoint(StartPoint.X, StartPoint.Y));
            col.Add(new StylusPoint(EndPoint.X, EndPoint.Y));
            System.Windows.Ink.Stroke ret = new System.Windows.Ink.Stroke(col);

            return ret;
        }

        public Intersection Intersects(Segment s)
        {
            Intersection ret = null;



            if (IsInRange(s))
            {
                ret = null;
            }
            else
            {

                Vector E1_S2 = s.StartPoint - EndPoint;
                Vector E1_E2 = s.EndPoint - EndPoint;
                Vector S1_S2 = s.StartPoint - StartPoint;
                Vector S1_E2 = s.EndPoint - StartPoint;

                //Extremity intersections
                if ((S1_S2).Length < MaxDist)
                {
                    ret = new Intersection();
                    ret.AddSegment(this, IntersectionType.StartIntersection);
                    ret.AddSegment(s, IntersectionType.StartIntersection);
                    ret.IntersectionPoint = StartPoint;
                    return ret;
                }
                else if ((S1_E2).Length < MaxDist)
                {
                    ret = new Intersection();
                    ret.AddSegment(this, IntersectionType.StartIntersection);
                    ret.AddSegment(s, IntersectionType.EndIntersection);
                    ret.IntersectionPoint = StartPoint;
                    return ret;
                }
                else if ((E1_E2).Length < MaxDist)
                {
                    ret = new Intersection();
                    ret.AddSegment(this, IntersectionType.EndIntersection);
                    ret.AddSegment(s, IntersectionType.EndIntersection);
                    ret.IntersectionPoint = EndPoint;
                    return ret;
                }
                else if ((E1_S2).Length < MaxDist)
                {
                    ret = new Intersection();
                    ret.AddSegment(this, IntersectionType.EndIntersection);
                    ret.AddSegment(s, IntersectionType.StartIntersection);
                    ret.IntersectionPoint = EndPoint;
                    return ret;
                }
                //middle to extremity intersections

                Point p1, p2, p3, p4;
                double currlength = MaxDist;

                if (TryGetClosestPoint(this, s.StartPoint, out p1))
                {
                    double case1 = (s.StartPoint - p1).Length;
                    if (case1 < currlength)
                    {
                        currlength = case1;
                        ret = new Intersection();
                        ret.AddSegment(this, IntersectionType.MiddleIntersection);
                        ret.AddSegment(s, IntersectionType.StartIntersection);
                        ret.IntersectionPoint = p1;
                    }
                }

                if (TryGetClosestPoint(this, s.EndPoint, out p2))
                {
                    double case2 = (s.EndPoint - p2).Length;
                    if (case2 < currlength)
                    {
                        currlength = case2;
                        ret = new Intersection();
                        ret.AddSegment(this, IntersectionType.MiddleIntersection);
                        ret.AddSegment(s, IntersectionType.EndIntersection);
                        ret.IntersectionPoint = p2;
                    }
                }

                if (TryGetClosestPoint(s, StartPoint, out p3))
                {
                    double case3 = (StartPoint - p3).Length;
                    if (case3 < currlength)
                    {
                        currlength = case3;
                        ret = new Intersection();
                        ret.AddSegment(this, IntersectionType.StartIntersection);
                        ret.AddSegment(s, IntersectionType.MiddleIntersection);
                        ret.IntersectionPoint = p3;
                    }
                }

                if (TryGetClosestPoint(s, EndPoint, out p4))
                {
                    double case4 = (EndPoint - p4).Length;
                    if (case4 < currlength)
                    {
                        currlength = case4;
                        ret = new Intersection();
                        ret.AddSegment(this, IntersectionType.EndIntersection);
                        ret.AddSegment(s, IntersectionType.MiddleIntersection);
                        ret.IntersectionPoint = p4;
                    }
                }


                #region old version

                //double middleIntersectionAngle = AngleThresohld;

                //if (Math.Abs(Vector.AngleBetween(E1_S2, E1_E2)) > middleIntersectionAngle)
                //{
                //    middleIntersectionAngle = Math.Abs(Vector.AngleBetween(E1_S2, E1_E2));
                //    ret = new Intersection();
                //    ret.AddSegment(this, IntersectionType.EndIntersection);
                //    ret.AddSegment(s, IntersectionType.MiddleIntersection);
                //    ret.IntersectionPoint = EndPoint;
                //}
                //if (Math.Abs(Vector.AngleBetween(S1_S2, S1_E2)) > middleIntersectionAngle)
                //{
                //    middleIntersectionAngle = Math.Abs(Vector.AngleBetween(S1_S2, S1_E2));
                //    ret = new Intersection();
                //    ret.AddSegment(this, IntersectionType.StartIntersection);
                //    ret.AddSegment(s, IntersectionType.MiddleIntersection);
                //    ret.IntersectionPoint = StartPoint;
                //}
                //if (Math.Abs(Vector.AngleBetween(S2_E1, S2_S1)) > middleIntersectionAngle)
                //{
                //    middleIntersectionAngle = Math.Abs(Vector.AngleBetween(S2_E1, S2_S1));
                //    ret = new Intersection();
                //    ret.AddSegment(this, IntersectionType.MiddleIntersection);
                //    ret.AddSegment(s, IntersectionType.StartIntersection);
                //    ret.IntersectionPoint = s.StartPoint;
                //}
                //if (Math.Abs(Vector.AngleBetween(E2_E1, E2_S1)) > middleIntersectionAngle)
                //{
                //    middleIntersectionAngle = Math.Abs(Vector.AngleBetween(E2_E1, E2_S1));
                //    ret = new Intersection();
                //    ret.AddSegment(this, IntersectionType.MiddleIntersection);
                //    ret.AddSegment(s, IntersectionType.EndIntersection);
                //    ret.IntersectionPoint = s.EndPoint;
                //}

                #endregion

            }
            return ret;
        }

        public bool IsInRange(Segment s)
        {
            return s.MostLeftX - MaxDist > MostRightX
                           || s.MostRightX + MaxDist < MostLeftX;
        }



        private Boolean TryGetClosestPoint(Segment Segment, Point p, out Point closest)
        {

            //http://www.gamedev.net/community/forums/topic.asp?topic_id=489006
            closest = new Point();
            Boolean ret = false;

            Vector v = Segment.ToVector();
            v.Normalize();

            double pv = v.X * p.X + v.Y * p.Y;
            double av = Segment.StartPoint.X * v.X + Segment.StartPoint.Y * v.Y;
            double d = pv - av;


            if (d <= 0)
            {
                ret = false;
            }
            else if (d > Segment.Length)
            {
                ret = false;
            }
            else
            {
                closest = Segment.StartPoint + d * v;
                ret = true;
            }

            return ret;
        }



        public void Split()
        {
            throw new System.NotImplementedException();
        }

        public void Concat(Segment s)
        {
            throw new System.NotImplementedException();
        }

        public Vector ToVector()
        {
            return (EndPoint - StartPoint);
        }

        public Vector ToNegateVector()
        {
            return (StartPoint - EndPoint);
        }

        #endregion

        #region IEquatable<Segment> Members

        public bool Equals(Segment other)
        {
            return EndPoint.Equals(other.EndPoint) && StartPoint.Equals(other.StartPoint);
        }

        #endregion

        #region NestedTypes


        public enum SegmentType
        {
            Line,
            Curve
        }



        #endregion

    }
}
